package service

import (
	"bytes"
	"errors"
	"fmt"
	"gin_gorm_oj/define"
	"gin_gorm_oj/helper"
	"gin_gorm_oj/models"
	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os/exec"
	"runtime"
	"strconv"
	"sync"
	"time"
)

// GetSubmitList 获取提交列表
// @Tags 公共方法
// @Summary 提交列表
// @Param page query int false "页码"
// @Param size query int false "每页大小"
// @Param problem_identity query string false "问题标识"
// @Param user_identity query string false "用户标识"
// @Param status query int false "status"
// @Success 200 {string} json {"code":"200","data":""}
// @Router /submit-list [get]
func GetSubmitList(c *gin.Context) {
	// 获取每页大小参数，默认为默认大小
	size, _ := strconv.Atoi(c.DefaultQuery("size", define.DefaultSize))

	// 获取页码参数，默认为默认页码
	page, err := strconv.Atoi(c.DefaultQuery("page", define.DefaultPage))
	if err != nil {
		log.Println("GetProblemList Page strconv Error:", err)
		return
	}

	page = (page - 1) * size  // 计算分页偏移量
	var count int64           // 定义总计数变量
	var data []*models.Submit // 声明存储查询结果的数据切片

	problemIdentity := c.Query("problem_identity") // 获取问题标识查询参数
	userIdentity := c.Query("user_identity")       // 获取用户标识查询参数
	status, _ := strconv.Atoi(c.Query("status"))   // 获取状态参数并转换为整数类型

	// 调用模型层获取提交列表查询对象
	tx := models.GetSubmitList(problemIdentity, userIdentity, status)
	// 执行查询：统计总数 + 分页查询 + 获取结果
	err = tx.Count(&count).Debug().Offset(page).Limit(size).Find(&data).Error
	if err != nil {
		// 记录错误日志
		log.Println("Get Problem List Error:", err)

		// 返回错误响应
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "Get Problem List Error:" + err.Error(),
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"code":  200,
		"data":  data,
		"count": count,
	})
}

func Submit(c *gin.Context) {
	// 获取问题标识参数
	problemIdentity := c.Query("problem_identity")

	// 读取请求体中的代码内容
	code, err := ioutil.ReadAll(c.Request.Body)
	if err != nil {
		// 读取失败，返回错误信息
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "Read Code Error:" + err.Error(),
		})
		return // 终止执行
	}

	// 代码保存
	path, err := helper.CodeSave(code) // 调用代码保存函数，返回文件路径
	if err != nil {                    // 如果保存失败
		// 返回错误响应
		c.JSON(http.StatusOK, gin.H{
			"code": -1,                               // 错误状态码
			"msg":  "Code Save Error:" + err.Error(), // 错误信息（包含具体错误）
		})
		return // 终止后续执行
	}
	// 获取当前用户信息
	u, _ := c.Get("user")
	userClaim := u.(*helper.UserClaims)

	// 构建提交记录对象
	sb := &models.Submit{
		Identity:        helper.GetUUID(),   // 生成唯一提交ID
		ProblemIdentity: problemIdentity,    // 关联问题ID
		UserIdentity:    userClaim.Identity, // 提交用户ID
		Path:            path,               // 代码文件路径
	}

	// 根据问题标识查询问题详情
	pb := &models.Problem{}
	err = models.DB.Where("identity=?", problemIdentity).Preload("TestCases").First(pb).Error
	if err != nil {
		// 查询失败，返回错误信息
		c.JSON(http.StatusOK, gin.H{
			"code": -1,
			"msg":  "问题查询失败：" + err.Error(),
		})
		return
	}
	WA := make(chan int)  // 错误答案通道：用于接收错误答案信号
	OOM := make(chan int) // 内存溢出通道：用于接收内存溢出信号
	passCount := 0        // 通过测试计数器：记录通过的测试用例数量
	CE := make(chan int)  // 编译错误的channel
	var (
		lock sync.Mutex
	)
	msg := ""
	fmt.Println(msg)

	// 遍历所有测试用例
	for _, testCase := range pb.TestCases {
		testCase := testCase // 创建局部变量副本，避免并发竞争
		// 为每个测试用例启动goroutine并发执行
		go func() {
			// 执行测试逻辑
			// 执行go run命令运行代码文件
			cmd := exec.Command("go", "run", path)
			// 创建缓冲区用于捕获标准输出和错误输出
			var out, stderr bytes.Buffer
			cmd.Stderr = &stderr
			cmd.Stdout = &out
			// 获取标准输入管道
			stdinPipe, err := cmd.StdinPipe()
			if err != nil {
				log.Fatalln(err)
			}
			// 向程序输入测试数据
			_, err = io.WriteString(stdinPipe, testCase.Input)
			if err != nil {
				return
			}
			// 读取内存统计信息
			var bm runtime.MemStats   // 声明内存统计结构体变量
			runtime.ReadMemStats(&bm) // 获取当前内存使用情况（传入指针地址）
			// 运行命令并检查错误
			if err := cmd.Run(); err != nil {
				// 打印错误日志：错误对象和标准错误输出
				log.Println(err, stderr.String())
				// 判断是否为编译错误（Go编译失败退出码为2）
				if err.Error() == "exit status 2" {
					_ = stderr.String() // 获取具体的错误信息
					CE <- 1             // 发送编译错误信号到CE通道
					return              // 立即返回，终止当前goroutine
				}
			}
			var em runtime.MemStats
			runtime.ReadMemStats(&em)
			if em.Alloc-bm.Alloc > 1000000000 {
				OOM <- 1
				return
			}
			// 答案错误
			if testCase.Output != out.String() { // 判断程序输出与期望输出是否一致
				_ = "答案错误" // 设置错误信息为"答案错误"
				WA <- 1    // 向WA通道发送信号，表示答案错误
				return     // 立即返回，终止当前测试用例的执行
			}
			// 内存使用检测：判断当前内存占用是否超过题目限制
			if em.Alloc/1024-(bm.Alloc/1024) > uint64(pb.MaxMem) {
				_ = "运行超内存" // 设置错误信息
				OOM <- 1    // 发送内存溢出信号
				return      // 终止当前测试
			}

			lock.Lock()   // 加锁：保护共享变量，防止并发竞争
			passCount++   // 计数：通过测试用例数量+1（临界区操作）
			lock.Unlock() // 解锁：释放锁，允许其他goroutine访问
		}()
	}

	select {
	// -1-待判断，1-答案正确，2-答案错误，3-运行超时，4-运行超内存
	case <-WA:
		sb.Status = 2 // 答案错误
		msg = "答案错误"
	case <-OOM:
		sb.Status = 4 // 运行超内存
		msg = "运行超内存"
	case <-CE:
		sb.Status = 5
		msg = "编译错误"
	case <-time.After(time.Millisecond * time.Duration(pb.MaxRuntime)):
		if passCount == len(pb.TestCases) {
			sb.Status = 1 // 所有测试用例通过，状态设为1（答案正确）
		} else {
			sb.Status = 3 // 未通过所有测试用例，状态设为3（运行超时）
		}
		sb.Status = 3 // 运行超时
		msg = "运行超时"
	}

	// 使用数据库事务执行代码提交操作
	if err = models.DB.Transaction(func(tx *gorm.DB) error {
		// 保存提交记录到数据库
		err = models.DB.Create(sb).Error
		err = tx.Create(sb).Error
		if err != nil {
			return errors.New("Submit Save Error:" + err.Error())
		}
		m := make(map[string]interface{})
		m["submit_num"] = gorm.Expr("submit_num + ?", 1)
		if sb.Status == 1 {
			m["pass_num"] = gorm.Expr("pass_num + ?", 1)
		}
		// 更新 user
		err = tx.Model(&models.User{}).Where("identity = ?", userClaim.Identity).Updates(m).Error
		if err != nil {
			return errors.New("User Modify Error:" + err.Error())
		}
		// 更新 problem
		// 更新问题基础信息的提交统计数据
		err = tx.Model(&models.Problem{}).
			Where("identity = ?", problemIdentity). // 根据问题唯一标识定位记录
			Updates(m).Error                        // 使用映射m更新字段
		if err != nil {
			// 更新失败，返回详细错误信息
			return errors.New("Problem Modify Error:" + err.Error())
		}
		return nil
		// 如果返回nil，事务会自动提交
		return nil
	}); err != nil { // 判断事务执行是否出错
		// 事务执行失败，返回错误响应
		c.JSON(http.StatusOK, gin.H{
			"code": -1,                            // 错误状态码
			"msg":  "Submit Error:" + err.Error(), // 错误信息（包含具体错误详情）
		})
		return // 终止后续处理
	}
	// 返回提交结果响应
	c.JSON(http.StatusOK, gin.H{
		"code": 200, // 状态码：成功
		"data": map[string]interface{}{
			"status": sb.Status, // 提交状态（如：判题中/已完成等）
		},
	})
}
